#ifndef ICURVE_H
#define ICURVE_H

#include <vector>
#include <algorithm>
#include <cassert>
#include "Curves/PointUtilities.h"

using namespace std;
using namespace Utilities;

template< class IPoint >
class ICurve
{
protected:
    /* Fields. */
    vector< IPoint > control_points;
    vector< IPoint > computed_points;
    vector< IPoint > convex_hull_computed_points;
    bool computed_points_up_to_date;
    bool convex_hull_up_to_date;

public:
    /* Constructors. */
    ICurve( int degree );
    /* Destructors. */
    virtual ~ICurve( );

    /* Methods. */
    virtual void AddPoint( const IPoint& point, double weight );
    virtual void AddPoint( const IPoint& point, int point_index, double weight );

    virtual void RemovePoint( );
    virtual void RemovePoint( int point_index );

    void SetPoint( int point_index, const IPoint& point );
    virtual void MovePoint( int point_index, const IPoint& vector );
    void MoveCurve( const IPoint& vector );
    void Rotate( double angle, const IPoint& central_point );
    void RotateAtCenter( double angle );

    bool Exists( const IPoint& point ) const;

    virtual IPoint Eval( double t ) const = 0;
    virtual vector< IPoint > ComputePoints( int n ) = 0;

    /* Getters. */
    vector< IPoint > ControlPoints( ) const;
    virtual vector< IPoint > ConvexHull( );
};

/* Constructors. */
template< class IPoint >
ICurve< IPoint >::ICurve( int degree = 1 )
{
    computed_points_up_to_date = false;
    convex_hull_up_to_date = false;
}

/* Destructors. */
template< class IPoint >
ICurve< IPoint >::~ICurve( )
{
}

/* Methods. */
template < class IPoint >
void ICurve< IPoint >::AddPoint( const IPoint& point, double weight = 1.0 )
{
    control_points.push_back( point );

    computed_points_up_to_date = false;
    convex_hull_up_to_date     = false;
}

template < class IPoint >
void ICurve< IPoint >::AddPoint( const IPoint& point, int point_index, double weight = 1.0 )
{
    typename vector< IPoint >::iterator it = control_points.begin() + point_index;

    control_points.insert( it, 1, point );

    computed_points_up_to_date = false;
    convex_hull_up_to_date     = false;
}

template < class IPoint >
void ICurve< IPoint >::RemovePoint( )
{
    assert( !control_points.empty( ) );

    control_points.pop_back( );

    computed_points_up_to_date = false;
    convex_hull_up_to_date     = false;
}

template < class IPoint >
void ICurve< IPoint >::RemovePoint( int point_index )
{
    assert( point_index >= 0 && point_index < static_cast< int >( control_points.size( ) ) );

    typename vector< IPoint >::iterator it = control_points.begin( ) + point_index;

    copy( it + 1, control_points.end( ), // Source
          it );                          // Destination

    control_points.pop_back();

    computed_points_up_to_date = false;
    convex_hull_up_to_date     = false;
}

template < class IPoint >
void ICurve< IPoint >::SetPoint( int point_index, const IPoint& point )
{
    control_points[point_index] = point;
    computed_points_up_to_date      = false;
    convex_hull_up_to_date          = false;
}

template < class IPoint >
void ICurve< IPoint >::MovePoint( int point_index, const IPoint& vector )
{
    control_points[point_index] += vector;
    computed_points_up_to_date       = false;
    convex_hull_up_to_date           = false;
}

template < class IPoint >
void ICurve< IPoint >::MoveCurve( const IPoint& vector )
{
    for (unsigned int i = 0; i < control_points.size( ); i++)
        control_points[i] += vector;

    computed_points_up_to_date = false;
    convex_hull_up_to_date     = false;
}

template < class IPoint >
void ICurve< IPoint >::Rotate( double angle, const IPoint& central_point )
{
    IPointRotateMultiple( control_points, central_point, angle );

    computed_points_up_to_date = false;
    convex_hull_up_to_date     = false;
}

template < class IPoint>
void ICurve< IPoint >::RotateAtCenter( double angle )
{
    IPointRotateMultiple( control_points, IPointCenterOfMass( control_points ), angle );

    computed_points_up_to_date = false;
    convex_hull_up_to_date     = false;
}

template < class IPoint >
bool ICurve< IPoint >::Exists( const IPoint& point ) const
{
    typename vector< IPoint >::const_iterator it =
            find( control_points.begin( ), control_points.end( ), point );

    if ( it == control_points.end() )
        return false;

    return true;
}

//template < class IPoint >
//vector< IPoint > ICurve< IPoint >::ComputePoints( int n ) const
//{
//    vector< IPoint > points( n );

//    for ( int i = 0; i < n; i++ )
//        points[i] = Eval( static_cast<double>( i ) / ( n - 1 ) );

//    return points;
//}

/* Getters. */
template < class IPoint >
vector< IPoint > ICurve< IPoint >::ControlPoints( ) const
{
    return this -> control_points;
}

template < class IPoint >
vector< IPoint > ICurve< IPoint >::ConvexHull( )
{
    if ( this -> convex_hull_up_to_date )
        return this -> convex_hull_computed_points;

    this -> convex_hull_computed_points = IPointConvexHull< IPoint >( control_points );
    this -> convex_hull_up_to_date      = true;

    return this -> convex_hull_computed_points;
}

#endif // ICURVE_H
